<?php

namespace plugin\voice\app\controller;

use support\Request;
use support\Response;
use Webman\Medoo\Medoo as db;
use Workerman\Connection\AsyncTcpConnection;
use Workerman\Protocols\Http\Chunk;
use Workerman\Protocols\Ws;

class ChatController{
    
    public function history(Request $request){
        $historyList    = db::select('voice_chat','*',[
            'group_id'  => $request->post('group_id'),
            'user_id'   => $request->userInfo['id'],
        ]);
        $messages       = [];
        foreach ($historyList as $history) {
            $message    = ['role' => 'user', 'content' => $history['question']];
            $messages[] = $message;
            $message    = ['role' => 'assistant', 'content' => $history['answer']];
            $messages[] = $message;
        }
        return json([
            'code'      => 200,
            'msg'       => 'ok',
            'data'      => $messages
        ]);
    }
    
    public function guid(){
        // mt_srand((double)microtime()*10000);
        mt_srand(round((double)microtime()*10000));
        $charid = strtoupper(md5(uniqid(rand(), true)));
        $hyphen = chr(45);
        $uuid   = substr($charid, 0, 8).$hyphen.substr($charid, 8, 4).$hyphen.substr($charid,12, 4).$hyphen.substr($charid,16, 4).$hyphen.substr($charid,20,12);
        return $uuid;
    }
    
    public function groupList(Request $request){
        $groupName     = $request->post('group_name')?:'';
        if($groupName){
            $groupList = db::select('voice_chat_groups',['chat_nums','group_id','group_name'],[
                'group_name[~]' => $groupName, 
                'user_id'       => $request->userInfo['id'], 
                'LIMIT'         => 30, 
                'ORDER'         => ['ID' => 'DESC']
            ]);
        }else{
            $groupList = db::select('voice_chat_groups',['chat_nums','group_id','group_name'],[
                'user_id' => $request->userInfo['id'], 
                'LIMIT' => 30, 
                'ORDER' => ['ID' => 'DESC']
            ]);
        }
        
        $groupInfo         = db::get('voice_chat_groups','*',['group_name' => '', 'user_id' => $request->userInfo['id']]);
        if(!$groupInfo){
            db::insert('voice_chat_groups',[
                'user_id'  => $request->userInfo['id'],
                'group_id' => self::guid()
            ]);
            $groupInfo     = db::get('voice_chat_groups','*',['group_name' => '', 'user_id' => $request->userInfo['id']]);
        }
        return json([
            'code' => 200,
            'msg'  => 'ok',
            'data' => ['list' => $groupList, 'info' => $groupInfo]
        ]);
    }
    
    public function index(Request $request): ?Response
    {
        $messages                  = $request->post('messages');
        $firstMessage              = current($messages);
        $checkMessages             = [];
        if ($firstMessage['role']  === 'system') {
            $checkMessages         = [$firstMessage];
        }
        if (!$checkMessages || count($messages) > 1) {
            $checkMessages[]       = array_slice($request->post('messages'), -1);
        }
        //缺一个敏感词接口
        //敏感词检测完毕
        $groupId                   = $request->post('group_id');
        $roleId                    = $request->post('role_id');
        $userId                    = $request->userInfo['id'];
        $content                   = last($messages)['content'] ?? '';
        $contentType               = $request->post('content_type');
        $realIp                    = $request->getRealIp();
        $modelInfo                 = db::get('voice_models','*',['id' => $request->post('model')]);
        $model                     = $modelInfo['model'];
        $request->model            = $modelInfo;
        $chatId                    = self::guid();
        // 保存聊天记录
        $chatData                  = [];
        $chatData['group_id']      = $groupId;
        $chatData['chat_id']       = $chatId;
        $chatData['user_id']       = $userId;
        $chatData['question']      = $content;
        $chatData['content_type']  = $contentType;
        $chatData['model']         = $model;
        $chatData['role_id']       = $roleId;
        $chatData['ip']            = $request->getRealIp();
        $chatData['status']        = 1;
        db::insert('voice_chat',$chatData);
        // 处理分组 
        $groupInfo                 = db::get('voice_chat_groups','group_name',['group_id' => $groupId]);
        if(!$groupInfo){
            switch ($contentType) {
                case 1:
                    $group_name    = iconv_substr($chatData['question'], 0, 10, 'UTF-8');
                    break;
                case '2':
                    $group_name    = "[语音]";
                    break;
                case '3':
                    $group_name    = "[图片]";
                    break;
                case '4':
                    $group_name    = "[文件]";
                    break;
                default:
                    $group_name    = iconv_substr($chatData['question'], 0, 10, 'UTF-8');
                    break;
            }
            db::update('voice_chat_groups',['group_name' => $group_name],['group_id' => $groupId]);
        }
        // 扣减次数
        if($request->userInfo[$modelInfo['field']] > 0){
            db::update('voice_users',[$modelInfo['field'].'[-]' =>1],['id' => $request->userInfo['id']]);
        }else{
            return json([
                'code' => 203,
                'msg'  => '可用次数不足,请充值'
            ]);
        }
        
        $stream                 = $request->post('stream');
        $connection             = $request->connection;
        $temperature            = $request->post('temperature');

        return $this->chat([
            'model'             => $model,
            'temperature'       => $temperature,
            'messages'          => $messages,
            'stream'            => $stream,
            'request'           => $request,
            'connection'        => $connection
        ], function ($responseText) use ($chatId) {
            // 更新聊天记录
            $chatData           = [];
            $chatData['answer'] = $responseText;
            $chatData['status'] = 2;
            db::update('voice_chat',$chatData,['chat_id' => $chatId]);
        });
    }
    
    /**
     * 聊天
     * @param $options
     * @param null $callback
     * @return Response
     * @throws BusinessException|Throwable
     */
    protected function chat($options, $callback = null): Response
    {
        $model              = $options['model'];
        $temperature        = max(0.1, (float)($options['temperature']??0.1));
        $messages           = $options['messages'];
        $stream             = (bool)($options['stream']??false);
        $request            = $options['request']??null;
        $connection         = $options['connection'];
        $handler            = "\\plugin\\voice\\app\\modelHandler\\" . ucfirst($options['request']->model['field']);
        if (!class_exists($handler)) {
            return json([
                'code' => 202,
                'msg'  => '当前模型未配置'
            ]);
        }
        $handler            = new $handler($request);
        [$schema, $address] = $handler->getApi;
        $con                = new AsyncTcpConnection("tcp://$address", ['ssl' => [
            'verify_peer' => false,
        ]]);
        $con->transport     = in_array($schema, ['wss', 'https']) ? 'ssl' : 'tcp';
        if ($isWebsocket    = in_array($schema, ['wss', 'ws'])) {
            $con->protocol  = Ws::class;
        }

        $buffer             = $handler->buildData([$model, $messages, $temperature, $stream]);
        //echo $buffer . "\n";
        $con->send($buffer);
        $con->buffer        = '';
        // 失败时
        $con->onError       = function ($con, $code, $msg) use ($connection) {
            echo $code, $msg;
            $con->buffer = $msg;
            if ($connection) {
                $connection->send(new Chunk(json_encode(['error' => ['message' => $msg]])));
                $connection->send(new Chunk(''));
            }
        };
        // api接口返回数据时
        $con->onMessage = function ($con, $buffer) use ($connection, $handler, $isWebsocket) {
            static $headerCompleted = false, $header = '', $bodyLength = 0, $sentLength = false;
            if (!$isWebsocket && !$headerCompleted) {
                $header .= $buffer;
                if (!$position = strpos($header, "\r\n\r\n")) {
                    return;
                }
                if (preg_match("/Content-Length: (\d+)\r\n/i", $header, $match)) {
                    $bodyLength = $match[1];
                }
                if(!$buffer = substr($header, $position + 4)) {
                    return;
                }
                $headerCompleted = true;
                if ($bodyLength) {
                    $con->buffer .= $buffer;
                    if ($connection) {
                        $sentLength += strlen($buffer);
                        $connection->send(new Chunk($buffer));
                        if ($sentLength >= $bodyLength) {
                            $connection->send(new Chunk(''));
                        }
                    }
                    return;
                }
            }
            $buffer = $isWebsocket ? "data: $buffer\n" : $buffer;
            $con->buffer .= $buffer;
            if ($connection) {
                if ($isWebsocket) {
                    $connection->send(new Chunk($buffer));
                } else {
                    if ($bodyLength) {
                        $sentLength += strlen($buffer);
                        $connection->send(new Chunk($buffer));
                        if ($sentLength >= $bodyLength) {
                            $connection->send(new Chunk(''));
                        }
                    } else {
                        $connection->send($buffer, true);
                    }
                }
            }
        };
        $con->onClose = function ($con) use ($callback, $model, $handler, $isWebsocket, $connection) {
            if ($connection && $isWebsocket) {
                $connection->send(new Chunk(''));
            }
            if ($con->buffer && $callback) {
                call_user_func($callback, $handler->formatResponse($con->buffer));
            }
        };
        $con->connect();
        // 向浏览器发送头部响应
        return response("\n")->withHeaders([
            "Content-Type" => "application/octet-stream",
            "Transfer-Encoding" => "chunked",
        ]);
    }
    
}
